package com.rs.game.npc;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import com.rs.Settings;
import com.rs.cache.loaders.ItemDefinitions;
import com.rs.cache.loaders.NPCDefinitions;
import com.rs.cores.CoresManager;
import com.rs.game.Animation;
import com.rs.game.Entity;
import com.rs.game.Graphics;
import com.rs.game.Hit;
import com.rs.game.Hit.HitLook;
import com.rs.game.SecondaryBar;
import com.rs.game.World;
import com.rs.game.WorldTile;
import com.rs.game.item.Item;
import com.rs.game.npc.combat.NPCCombat;
import com.rs.game.npc.combat.NPCCombatDefinitions;
import com.rs.game.npc.familiar.Familiar;
import com.rs.game.player.Player;
import com.rs.game.player.SlayerManager;
import com.rs.game.player.content.FriendChatsManager;
import com.rs.game.player.content.RingOfWealth;
import com.rs.game.player.controlers.Wilderness;
import com.rs.game.tasks.WorldTask;
import com.rs.game.tasks.WorldTasksManager;
import com.rs.game.world.area.Area;
import com.rs.utils.Logger;
import com.rs.utils.MapAreas;
import com.rs.utils.NPCBonuses;
import com.rs.utils.NPCCombatDefinitionsL;
import com.rs.utils.NPCDrops;
import com.rs.utils.Utils;

public class NPC extends Entity implements Serializable {

    public static int NORMAL_WALK = 0x2, WATER_WALK = 0x4, FLY_WALK = 0x8;

    private static final long serialVersionUID = -4794678936277614443L;

    private int id;
    private WorldTile respawnTile;
    private int mapAreaNameHash;
    private boolean canBeAttackFromOutOfArea;
    private int[] bonuses; // 0 stab, 1 slash, 2 crush,3 mage, 4 range, 5 stab
    // def, blahblah till 9
    private boolean spawned;
    private transient NPCCombat combat;
    public WorldTile forceWalk;
	private boolean randomwalk;

    private long lastAttackedByTarget;
    private boolean cantInteract;
    private int capDamage;
    private int lureDelay;
    private boolean cantFollowUnderCombat;
    private boolean forceAgressive;
    private int forceTargetDistance;
    private boolean forceFollowClose;
    private boolean forceMultiAttacked;
    private boolean noDistanceCheck;
    private boolean intelligentRouteFinder;

    // npc masks
    private transient Transformation nextTransformation;
    private transient SecondaryBar nextSecondaryBar;
    // name changing masks
    private String name;
    private transient boolean changedName;
    private int combatLevel;
    private transient boolean changedCombatLevel;
    private transient boolean locked;
    private transient double dropRateFactor;

    public NPC(int id, WorldTile tile, int mapAreaNameHash, boolean canBeAttackFromOutOfArea) {
	this(id, tile, mapAreaNameHash, canBeAttackFromOutOfArea, false);
    }
    

    /*
     * creates and adds npc
     */
    public NPC(int id, WorldTile tile, int mapAreaNameHash, boolean canBeAttackFromOutOfArea, boolean spawned) {
	super(tile);
	this.id = id;
	this.respawnTile = new WorldTile(tile);
	this.mapAreaNameHash = mapAreaNameHash;
	this.canBeAttackFromOutOfArea = canBeAttackFromOutOfArea;
	this.spawned = spawned;
	combatLevel = -1;
	setHitpoints(getMaxHitpoints());
	setDirection(getRespawnDirection());
	//int walkType = t(id);
	setRandomWalk((getDefinitions().walkMask & 0x2) != 0 || forceRandomWalk(id));
	setBonuses();
	combat = new NPCCombat(this);
	capDamage = -1;
	lureDelay = 12000;
	// npc is inited on creating instance
	initEntity();
	World.addNPC(this);
	World.updateEntityRegion(this);
	// npc is started on creating instance
	loadMapRegions();
	checkMultiArea();
    }

	private static boolean forceRandomWalk(int npcId) {
		switch (npcId) {
		case 11226:
		case 3341:
		case 3342:
		case 3343:
			return true;
		default:
			return false;
		}
	}

	public void setBonuses() {
	bonuses = NPCBonuses.getBonuses(id);
	if (bonuses == null) {
	    bonuses = new int[10];
	    int level = getCombatLevel();
	    for (int i = 0; i < bonuses.length; i++)
		bonuses[i] = level;
	}
    }

    @Override
    public boolean needMasksUpdate() {
	return super.needMasksUpdate() || nextSecondaryBar != null || nextTransformation != null || getCustomName() != null || getCustomCombatLevel() >= 0 /*									        * changedName
															        */;
    }

    public NPC(int id, WorldTile tile) {
    	  super(tile);
    	  this.id = id;
    	 }

	public void setNextNPCTransformation(int id) {
	setNPC(id);
	nextTransformation = new Transformation(id);
	if (getCustomCombatLevel() != -1)
	    changedCombatLevel = true;
	if (getCustomName() != null)
	    changedName = true;
    }

    public void setNPC(int id) {
	this.id = id;
	setBonuses();
    }

    @Override
    public void resetMasks() {
	super.resetMasks();
	nextTransformation = null;
	changedCombatLevel = false;
	changedName = false;
	nextSecondaryBar = null;
    }

    public int getMapAreaNameHash() {
	return mapAreaNameHash;
    }

    public void setCanBeAttackFromOutOfArea(boolean b) {
	canBeAttackFromOutOfArea = b;
    }

    public boolean canBeAttackFromOutOfArea() {
	return canBeAttackFromOutOfArea;
    }

    public NPCDefinitions getDefinitions() {
	return NPCDefinitions.getNPCDefinitions(id);
    }

    public NPCCombatDefinitions getCombatDefinitions() {
	return NPCCombatDefinitionsL.getNPCCombatDefinitions(id);
    }

    @Override
    public int getMaxHitpoints() {
	return getCombatDefinitions().getHitpoints();
    }

    public int getId() {
	return id;
    }

    public void processNPC() {
	if (isDead() || locked)
	    return;
	switch (id) {
	case 9367:
		setName("Food & Pots");
		break;
	case 14414:
		setName("Vote Store");
		break;
	case 4965:
		setName("Achievement Store");
		break;
		
	case 13928:
		setName("Skilling Shops");
		break;
	case 15533:
		setName("Ranging Shops");
		break;
	case 15582:
		setName("Magic Shops");
		break;
	case 14912:
		setName("Melee Shops");
		break;
	case 456:
		setName("Prayerbooks");
		break;
	case 881:
		setName("Spellbooks");
		break;
	case 9047:
		setName("Donator Store");
		break;
	case 4286:
		setName("Loyalty Store");
		break;
	case 14282:
		setName("Slayer Veteran");
		break;
	case 4585:
		setRandomWalk(false);
		setName("Teleporter");
		break;
	}
	if (!combat.process()) { // if not under combat
		
	    if (!isForceWalking()) {// combat still processed for attack delay

	    	if (!cantInteract) {
	    		if (!checkAgressivity()) {
	    			if (getFreezeDelay() < Utils.currentTimeMillis()) {
	    				if (((hasRandomWalk()) && World.getMask(getPlane(), getX(), getY()) == 0) && Math.random() * 1000.0 < 100.0) {
	    					int moveX = (int) Math.round(Math.random() * 10.0 - 5.0);
	    					int moveY = (int) Math.round(Math.random() * 10.0 - 5.0);
	    					resetWalkSteps();
	    					if (getMapAreaNameHash() != -1) {
	    						if (!MapAreas.isAtArea(getMapAreaNameHash(), this)) {
	    							forceWalkRespawnTile();
	    							return;
	    						}
	    						addWalkSteps(getX() + moveX, getY() + moveY, 5);
	    					} else
	    						addWalkSteps(respawnTile.getX() + moveX, respawnTile.getY() + moveY, 5);
	    				}
	    			}
	    		}
	    	}
	    }
	}
	if (isForceWalking()) {
		if (getFreezeDelay() < Utils.currentTimeMillis()) {
			if (getX() != forceWalk.getX() || getY() != forceWalk.getY()) {
				if (!hasWalkSteps())
					addWalkSteps(forceWalk.getX(), forceWalk.getY(), getSize(), true);
				if (!hasWalkSteps()) { // failing finding route
					setNextWorldTile(new WorldTile(forceWalk)); // force
					forceWalk = null; // so ofc reached forcewalk place
				}
			} else
				// walked till forcewalk place
				forceWalk = null;
		}
	}
    }

    @Override
    public void processEntity() {
	super.processEntity();
	processNPC();
    }

	public boolean hasRandomWalk() {
		return randomwalk;
	}
	
    public int getRespawnDirection() {
	NPCDefinitions definitions = getDefinitions();
	if (definitions.anInt853 << 32 != 0 && definitions.respawnDirection > 0 && definitions.respawnDirection <= 8)
	    return (4 + definitions.respawnDirection) << 11;
	return 0;
    }

    /*
     * forces npc to random walk even if cache says no, used because of fake
     * cache information
     */
    /*  private static int walkType(int npcId) {
    switch (npcId) {
        case 11226:
    	return RANDOM_WALK;
        case 3341:
        case 3342:
        case 3343:
    	return RANDOM_WALK;
        default:
    	return -1;
    }
      }*/

    public void sendSoulSplit(final Hit hit, final Entity user) {
	final NPC target = this;
	if (hit.getDamage() > 0)
	    World.sendProjectile(user, this, 2263, 11, 11, 20, 5, 0, 0);
	user.heal(hit.getDamage() / 5);
	WorldTasksManager.schedule(new WorldTask() {
	    @Override
	    public void run() {
		setNextGraphics(new Graphics(2264));
		if (hit.getDamage() > 0)
		    World.sendProjectile(target, user, 2263, 11, 11, 20, 5, 0, 0);
	    }
	}, 1);
    }

    @Override
    public void handleIngoingHit(final Hit hit) {
	if (capDamage != -1 && hit.getDamage() > capDamage)
	    hit.setDamage(capDamage);
	if (hit.getLook() != HitLook.MELEE_DAMAGE && hit.getLook() != HitLook.RANGE_DAMAGE && hit.getLook() != HitLook.MAGIC_DAMAGE)
	    return;
	Entity source = hit.getSource();
	if (source == null)
	    return;
	if (source instanceof Player) {
	    final Player p2 = (Player) source;
	    if (p2.getPrayer().hasPrayersOn()) {
		if (p2.getPrayer().usingPrayer(1, 18))
		    sendSoulSplit(hit, p2);
		if (hit.getDamage() == 0)
		    return;
		if (!p2.getPrayer().isBoostedLeech()) {
		    if (hit.getLook() == HitLook.MELEE_DAMAGE) {
			if (p2.getPrayer().usingPrayer(1, 19)) {
			    p2.getPrayer().increaseTurmoilBonus(this);
			    p2.getPrayer().setBoostedLeech(true);
			    return;
			} else if (p2.getPrayer().usingPrayer(1, 1)) { // sap
			    // att
			    if (Utils.getRandom(4) == 0) {
				if (p2.getPrayer().reachedMax(0)) {
				    p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your sap curse has no effect.", true);
				} else {
				    p2.getPrayer().increaseLeechBonus(0);
				    p2.getPackets().sendGameMessage("Your curse drains Attack from the enemy, boosting your Attack.", true);
				}
				p2.setNextAnimation(new Animation(12569));
				p2.setNextGraphics(new Graphics(2214));
				p2.getPrayer().setBoostedLeech(true);
				World.sendProjectile(p2, this, 2215, 35, 35, 20, 5, 0, 0);
				WorldTasksManager.schedule(new WorldTask() {
				    @Override
				    public void run() {
					setNextGraphics(new Graphics(2216));
				    }
				}, 1);
				return;
			    }
			} else {
			    if (p2.getPrayer().usingPrayer(1, 10)) {
				if (Utils.getRandom(7) == 0) {
				    if (p2.getPrayer().reachedMax(3)) {
					p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
				    } else {
					p2.getPrayer().increaseLeechBonus(3);
					p2.getPackets().sendGameMessage("Your curse drains Attack from the enemy, boosting your Attack.", true);
				    }
				    p2.setNextAnimation(new Animation(12575));
				    p2.getPrayer().setBoostedLeech(true);
				    World.sendProjectile(p2, this, 2231, 35, 35, 20, 5, 0, 0);
				    WorldTasksManager.schedule(new WorldTask() {
					@Override
					public void run() {
					    setNextGraphics(new Graphics(2232));
					}
				    }, 1);
				    return;
				}
			    }
			    if (p2.getPrayer().usingPrayer(1, 14)) {
				if (Utils.getRandom(7) == 0) {
				    if (p2.getPrayer().reachedMax(7)) {
					p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
				    } else {
					p2.getPrayer().increaseLeechBonus(7);
					p2.getPackets().sendGameMessage("Your curse drains Strength from the enemy, boosting your Strength.", true);
				    }
				    p2.setNextAnimation(new Animation(12575));
				    p2.getPrayer().setBoostedLeech(true);
				    World.sendProjectile(p2, this, 2248, 35, 35, 20, 5, 0, 0);
				    WorldTasksManager.schedule(new WorldTask() {
					@Override
					public void run() {
					    setNextGraphics(new Graphics(2250));
					}
				    }, 1);
				    return;
				}
			    }

			}
		    }
		    if (hit.getLook() == HitLook.RANGE_DAMAGE) {
			if (p2.getPrayer().usingPrayer(1, 2)) { // sap range
			    if (Utils.getRandom(4) == 0) {
				if (p2.getPrayer().reachedMax(1)) {
				    p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your sap curse has no effect.", true);
				} else {
				    p2.getPrayer().increaseLeechBonus(1);
				    p2.getPackets().sendGameMessage("Your curse drains Range from the enemy, boosting your Range.", true);
				}
				p2.setNextAnimation(new Animation(12569));
				p2.setNextGraphics(new Graphics(2217));
				p2.getPrayer().setBoostedLeech(true);
				World.sendProjectile(p2, this, 2218, 35, 35, 20, 5, 0, 0);
				WorldTasksManager.schedule(new WorldTask() {
				    @Override
				    public void run() {
					setNextGraphics(new Graphics(2219));
				    }
				}, 1);
				return;
			    }
			} else if (p2.getPrayer().usingPrayer(1, 11)) {
			    if (Utils.getRandom(7) == 0) {
				if (p2.getPrayer().reachedMax(4)) {
				    p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
				} else {
				    p2.getPrayer().increaseLeechBonus(4);
				    p2.getPackets().sendGameMessage("Your curse drains Range from the enemy, boosting your Range.", true);
				}
				p2.setNextAnimation(new Animation(12575));
				p2.getPrayer().setBoostedLeech(true);
				World.sendProjectile(p2, this, 2236, 35, 35, 20, 5, 0, 0);
				WorldTasksManager.schedule(new WorldTask() {
				    @Override
				    public void run() {
					setNextGraphics(new Graphics(2238));
				    }
				});
				return;
			    }
			}
		    }
		    if (hit.getLook() == HitLook.MAGIC_DAMAGE) {
			if (p2.getPrayer().usingPrayer(1, 3)) { // sap mage
			    if (Utils.getRandom(4) == 0) {
				if (p2.getPrayer().reachedMax(2)) {
				    p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your sap curse has no effect.", true);
				} else {
				    p2.getPrayer().increaseLeechBonus(2);
				    p2.getPackets().sendGameMessage("Your curse drains Magic from the enemy, boosting your Magic.", true);
				}
				p2.setNextAnimation(new Animation(12569));
				p2.setNextGraphics(new Graphics(2220));
				p2.getPrayer().setBoostedLeech(true);
				World.sendProjectile(p2, this, 2221, 35, 35, 20, 5, 0, 0);
				WorldTasksManager.schedule(new WorldTask() {
				    @Override
				    public void run() {
					setNextGraphics(new Graphics(2222));
				    }
				}, 1);
				return;
			    }
			} else if (p2.getPrayer().usingPrayer(1, 12)) {
			    if (Utils.getRandom(7) == 0) {
				if (p2.getPrayer().reachedMax(5)) {
				    p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
				} else {
				    p2.getPrayer().increaseLeechBonus(5);
				    p2.getPackets().sendGameMessage("Your curse drains Magic from the enemy, boosting your Magic.", true);
				}
				p2.setNextAnimation(new Animation(12575));
				p2.getPrayer().setBoostedLeech(true);
				World.sendProjectile(p2, this, 2240, 35, 35, 20, 5, 0, 0);
				WorldTasksManager.schedule(new WorldTask() {
				    @Override
				    public void run() {
					setNextGraphics(new Graphics(2242));
				    }
				}, 1);
				return;
			    }
			}
		    }

		    // overall

		    if (p2.getPrayer().usingPrayer(1, 13)) { // leech defence
			if (Utils.getRandom(10) == 0) {
			    if (p2.getPrayer().reachedMax(6)) {
				p2.getPackets().sendGameMessage("Your opponent has been weakened so much that your leech curse has no effect.", true);
			    } else {
				p2.getPrayer().increaseLeechBonus(6);
				p2.getPackets().sendGameMessage("Your curse drains Defence from the enemy, boosting your Defence.", true);
			    }
			    p2.setNextAnimation(new Animation(12575));
			    p2.getPrayer().setBoostedLeech(true);
			    World.sendProjectile(p2, this, 2244, 35, 35, 20, 5, 0, 0);
			    WorldTasksManager.schedule(new WorldTask() {
				@Override
				public void run() {
				    setNextGraphics(new Graphics(2246));
				}
			    }, 1);
			    return;
			}
		    }
		}
	    }
	}

    }

    @Override
    public void reset() {
	super.reset();
	setDirection(getRespawnDirection());
	combat.reset();
	setBonuses(); // back to real bonuses
	forceWalk = null;
    }

    @Override
    public void finish() {
	if (hasFinished())
	    return;
	setFinished(true);
	World.updateEntityRegion(this);
	World.removeNPC(this);
    }

    public void setRespawnTask() {
	if (!hasFinished()) {
	    reset();
	    setLocation(respawnTile);
	    finish();
	}
	CoresManager.slowExecutor.schedule(new Runnable() {
	    @Override
	    public void run() {
		try {
		    spawn();
		}
		catch (Throwable e) {
		    Logger.handle(e);
		}
	    }
	}, getCombatDefinitions().getRespawnDelay() * 600, TimeUnit.MILLISECONDS);
    }

    public void deserialize() {
	if (combat == null)
	    combat = new NPCCombat(this);
	spawn();
    }

    public void spawn() {
	setFinished(false);
	World.addNPC(this);
	setLastRegionId(0);
	World.updateEntityRegion(this);
	loadMapRegions();
	checkMultiArea();
    }

    public NPCCombat getCombat() {
	return combat;
    }

    @Override
    public void sendDeath(final Entity source) {
	final NPCCombatDefinitions defs = getCombatDefinitions();
	resetWalkSteps();
	combat.removeTarget();
	setNextAnimation(null);
	WorldTasksManager.schedule(new WorldTask() {
	    int loop;

	    @Override
	    public void run() {
		if (loop == 0) {
		    setNextAnimation(new Animation(defs.getDeathEmote()));
		} else if (loop >= defs.getDeathDelay()) {
		    if (source instanceof Player)
			((Player) source).getControlerManager().processNPCDeath(getId());
		    drop();
		    reset();
		    setLocation(respawnTile);
		    finish();
		    if (!isSpawned())
			setRespawnTask();
		    if (source.getAttackedBy() == NPC.this) { //no need to wait after u kill
			source.setAttackedByDelay(0);
			source.setAttackedBy(null);
			source.setFindTargetDelay(0);
		    }
		    stop();
		}
		loop++;
	    }
	}, 0, 1);
    }

    public void drop() {
    	try {
    		if (getCombatDefinitions() == NPCCombatDefinitionsL.DEFAULT_DEFINITION || getMaxHitpoints() == 1)
    			return;
    		Player killer = getMostDamageReceivedSourcePlayer();
    		if (killer == null)
    			return;
    		Player otherPlayer = killer.getSlayerManager().getSocialPlayer();
    		SlayerManager manager = killer.getSlayerManager();
    		if (manager.isValidTask(getName()))
    			manager.checkCompletedTask(getDamageReceived(killer), otherPlayer != null ? getDamageReceived(otherPlayer) : 0);
    		Drop[] drops = NPCDrops.getDrops(id);
    		if (drops == null)
    			return;
    		Drop[] possibleDrops = new Drop[drops.length];
    		int possibleDropsCount = 0;
			for (Drop drop : drops) {
				if (drop.getRate() == 100)
					sendDrop(killer, drop);
				else {
					if ((Utils.getRandomDouble(99) + 1) <= drop.getRate() * 1.3)
						possibleDrops[possibleDropsCount++] = drop;
				}
			}
			if (possibleDropsCount > 0)
				sendDrop(killer, possibleDrops[Utils.getRandom(possibleDropsCount - 1)]);
    		sendCharms(killer);
    	} catch (Exception e) {
    		e.printStackTrace();
    	} 
    }

	public boolean onDropTable(String npcName) {
		switch (npcName) {
		case "'Rum'-pumped crab":
		case "Aberrant spectre":
		case "Abyssal demon":
		case "Abyssal leech":
		case "Air elemental":
		case "Ancient mage":
		case "Ancient ranger":
		case "Ankou":
		case "Armoured zombie":
		case "Arrg":
		case "Astea Frostweb":
		case "Aviansie":
		case "Bandit":
		case "Banshee":
		case "Barbarian":
		case "Barbarian woman":
		case "Basilisk":
		case "Black Guard":
		case "Black Guard Berserker":
		case "Black Guard crossbowdwarf":
		case "Black Heather":
		case "Black Knight":
		case "Black Knight Titan":
		case "Black demon":
		case "Black dragon":
		case "Blood reaver":
		case "Bloodveld":
		case "Blue dragon":
		case "Bork":
		case "Brine rat":
		case "Bronze dragon":
		case "Brutal green dragon":
		case "Catablepon":
		case "Cave bug":
		case "Cave crawler":
		case "Cave horror":
		case "Cave slime":
		case "Chaos Elemental":
		case "Chaos druid":
		case "Chaos druid warrior":
		case "Chaos dwarf":
		case "Chaos dwarf hand cannoneer":
		case "Chaos dwogre":
		case "Cockatrice":
		case "Cockroach drone":
		case "Cockroach soldier":
		case "Cockroach worker":
		case "Columbarium":
		case "Columbarium key":
		case "Commander Zilyana":
		case "Corporeal Beast":
		case "Crawling Hand":
		case "Cyclops":
		case "Cyclossus":
		case "Dagannoth":
		case "Dagannoth Prime":
		case "Dagannoth Rex":
		case "Dagannoth Supreme":
		case "Dagannoth guardian":
		case "Dagannoth spawn":
		case "Dark beast":
		case "Desert Lizard":
		case "Desert strykewyrm":
		case "Dried zombie":
		case "Dust devil":
		case "Dwarf":
		case "Earth elemental":
		case "Earth warrior":
		case "Elf warrior":
		case "Elite Black Knight":
		case "Elite Dark Ranger":
		case "Elite Khazard guard":
		case "Exiled Kalphite Queen":
		case "Exiled kalphite guardian":
		case "Exiled kalphite marauder":
		case "Ferocious barbarian spirit":
		case "Fire elemental":
		case "Fire giant":
		case "Flesh Crawler":
		case "Forgotten Archer":
		case "Forgotten Mage":
		case "Forgotten Warrior":
		case "Frog":
		case "Frost dragon":
		case "Ganodermic beast":
		case "Gargoyle":
		case "General Graardor":
		case "General malpractitioner":
		case "Ghast":
		case "Ghostly warrior":
		case "Giant Mole":
		case "Giant ant soldier":
		case "Giant ant worker":
		case "Giant rock crab":
		case "Giant skeleton":
		case "Giant wasp":
		case "Glacor":
		case "Glod":
		case "Gnoeals":
		case "Goblin statue":
		case "Gorak":
		case "Greater demon":
		case "Greater reborn mage":
		case "Greater reborn ranger":
		case "Greater reborn warrior":
		case "Green dragon":
		case "Grotworm":
		case "Haakon the Champion":
		case "Harold":
		case "Harpie Bug Swarm":
		case "Hill giant":
		case "Hobgoblin":
		case "Ice giant":
		case "Ice strykewyrm":
		case "Ice troll":
		case "Ice troll female":
		case "Ice troll male":
		case "Ice troll runt":
		case "Ice warrior":
		case "Icefiend":
		case "Iron dragon":
		case "Jelly":
		case "Jogre":
		case "Jungle horror":
		case "Jungle strykewyrm":
		case "K'ril Tsutsaroth":
		case "Kalphite Guardian":
		case "Kalphite King":
		case "Kalphite Queen":
		case "Kalphite Soldier":
		case "Kalphite Worker":
		case "Killerwatt":
		case "King Black Dragon":
		case "Kraka":
		case "Kree'arra":
		case "Kurask":
		case "Lanzig":
		case "Lesser demon":
		case "Lesser reborn mage":
		case "Lesser reborn ranger":
		case "Lesser reborn warrior":
		case "Lizard":
		case "Locust lancer":
		case "Locust ranger":
		case "Locust rider":
		case "Mature grotworm":
		case "Mighty banshee":
		case "Minotaur":
		case "Mithril dragon":
		case "Molanisk":
		case "Moss giant":
		case "Mountain troll":
		case "Mummy":
		case "Mutated bloodveld":
		case "Mutated jadinko male":
		case "Mutated zygomite":
		case "Nechryael":
		case "Nex":
		case "Ogre":
		case "Ogre statue":
		case "Ork statue":
		case "Otherworldly being":
		case "Ourg statue":
		case "Paladin":
		case "Pee Hat":
		case "Pirate":
		case "Pyrefiend":
		case "Queen Black Dragon":
		case "Red dragon":
		case "Rock lobster":
		case "Rockslug":
		case "Salarin the Twisted":
		case "Scabaras lancer":
		case "Scarab mage":
		case "Sea Snake Hatchling":
		case "Shadow warrior":
		case "Skeletal Wyvern":
		case "Skeletal miner":
		case "Skeleton":
		case "Skeleton fremennik":
		case "Skeleton thug":
		case "Skeleton warlord":
		case "Small Lizard":
		case "Soldier":
		case "Sorebones":
		case "Speedy Keith":
		case "Spiritual mage":
		case "Spiritual warrior":
		case "Steel dragon":
		case "Stick":
		case "Suqah":
		case "Terror dog":
		case "Thrower Troll":
		case "Thug":
		case "Tortured soul":
		case "Trade floor guard":
		case "Tribesman":
		case "Troll general":
		case "Troll spectator":
		case "Tstanon Karlak":
		case "Turoth":
		case "Tyras guard":
		case "TzHaar-Hur":
		case "TzHaar-Ket":
		case "TzHaar-Mej":
		case "TzHaar-Xil":
		case "Undead troll":
		case "Vampyre":
		case "Vyre corpse":
		case "Vyrelady":
		case "Vyrelord":
		case "Vyrewatch":
		case "Wallasalki":
		case "Warped terrorbird":
		case "Warped tortoise":
		case "Warrior":
		case "Water elemental":
		case "Waterfiend":
		case "Werewolf":
		case "White Knight":
		case "WildyWyrm":
		case "Yeti":
		case "Yuri":
		case "Zakl'n Gritch":
		case "Zombie":
		case "Zombie hand":
		case "Zombie swab":
			return true;
		}
		return false;
	}
	
	public void sendDrop(Player player, RingOfWealth drop) {
		World.addGroundItem(new Item(drop.getItemId(), drop.getAmount()),
				new WorldTile(getCoordFaceX(getSize()),
						getCoordFaceY(getSize()), getPlane()), player, true,
				180);
	}
	
	public void dropCharm(Player player, double rarity, int charmId) {
		Drop charmDrop = new Drop(charmId, rarity, 1, 5, false);
		sendDrop(player, charmDrop);
		return;
	}

	public void dropCharm(Player player, double rarity, Item charm) {
		Drop charmDrop = new Drop(charm.getId(), rarity, charm.getAmount(), charm.getAmount(), false);
		sendDrop(player, charmDrop);
		return;
	}

	public void sendCharms(Player player) {
		int[] CHARMS = { 12159, 12160, 12158, 12163 };
		if (Utils.random(3) != 0) {
			dropCharm(player, 50, CHARMS[Utils.random(CHARMS.length)]);
			dropCharm(player, 25, new Item(995, Utils.random(1000, 10000)));
		}
	}

	public void sendDrop(Player player, Drop drop) {
		int size = getSize();
		String dropName = ItemDefinitions.getItemDefinitions(drop.getItemId())
				.getName().toLowerCase();
		Item item = 
				ItemDefinitions.getItemDefinitions(drop.getItemId()).isStackable() ?
						new Item(drop.getItemId(), (drop.getMinAmount() * Settings.DROP_RATE) + Utils.getRandom(drop.getExtraAmount() *Settings.DROP_RATE))
		:
			new Item(drop.getItemId(), drop.getMinAmount() + Utils.getRandom(drop.getExtraAmount()));
						FriendChatsManager fc = player.getCurrentFriendChat();
						World.addGroundItem(item, new WorldTile(getCoordFaceX(size),
								getCoordFaceY(size), getPlane()), player, true, 180);
						if (dropName.contains("pernix") || dropName.contains("torva")
								|| dropName.contains("virtus") || dropName.contains("bandos")
								|| dropName.contains("hilt") || dropName.contains("divine")
								|| dropName.contains("saradomin sword")
								|| dropName.contains("visage") || dropName.contains("spectral")
								|| dropName.contains("elysian")
								|| dropName.contains("zaryte")
								|| dropName.contains("arcane")
								|| dropName.contains("steadfast")
								|| dropName.contains("armadyl chest")
								|| dropName.contains("armadyl chain")
								|| dropName.contains("glaiven") || dropName.contains("steadfast")
								|| dropName.contains("ragefire")
								|| dropName.contains("spirit shield")
								|| dropName.contains("arcane")
								|| dropName.contains("bones")
								|| dropName.contains("dragon pickaxe")) {
							World.sendWorldMessage(
									"<col=ff8c38><img=7>News:</col><col=ff8c38> " + player.getDisplayName()
											+ "</col><col=ff8c38> has just recieved </col> <col=ff8c38>" + dropName + "<col=ff8c38> drop!"
											+ "</col> ", false);
						}
					}

    @Override
    public int getSize() {
	return getDefinitions().size;
    }
	
    public int getMaxHit() {
	return getCombatDefinitions().getMaxHit();
    }

    public int[] getBonuses() {
	return bonuses;
    }

    @Override
    public double getMagePrayerMultiplier() {
	return 0;
    }

    @Override
    public double getRangePrayerMultiplier() {
	return 0;
    }

    @Override
    public double getMeleePrayerMultiplier() {
	return 0;
    }

    public WorldTile getRespawnTile() {
	return respawnTile;
    }

    public boolean isUnderCombat() {
	return combat.underCombat();
    }

    @Override
    public void setAttackedBy(Entity target) {
	super.setAttackedBy(target);
	if (target == combat.getTarget() && !(combat.getTarget() instanceof Familiar))
	    lastAttackedByTarget = Utils.currentTimeMillis();
    }

    public boolean canBeAttackedByAutoRelatie() {
	return Utils.currentTimeMillis() - lastAttackedByTarget > lureDelay;
    }

    public boolean isForceWalking() {
	return forceWalk != null;
    }

    public void setTarget(Entity entity) {
	if (isForceWalking()) // if force walk not gonna get target
	    return;
	combat.setTarget(entity);
	lastAttackedByTarget = Utils.currentTimeMillis();
    }

    public void removeTarget() {
	if (combat.getTarget() == null)
	    return;
	combat.removeTarget();
    }

    public void forceWalkRespawnTile() {
	setForceWalk(respawnTile);
    }

    public void setForceWalk(WorldTile tile) {
	resetWalkSteps();
	forceWalk = tile;
    }

    public boolean hasForceWalk() {
	return forceWalk != null;
    }

    public ArrayList<Entity> getPossibleTargets(boolean checkNPCs, boolean checkPlayers) {
	int size = getSize();
	int agroRatio = getCombatDefinitions().getAgroRatio();
	ArrayList<Entity> possibleTarget = new ArrayList<Entity>();
	for (int regionId : getMapRegionsIds()) {
	    if (checkPlayers) {
		List<Integer> playerIndexes = World.getRegion(regionId).getPlayerIndexes();
		if (playerIndexes != null) {
		    for (int playerIndex : playerIndexes) {
			Player player = World.getPlayers().get(playerIndex);
			if (player == null || player.isDead() || player.hasFinished() || !player.isRunning() || player.getAppearence().isHidden() || !Utils.isOnRange(getX(), getY(), size, player.getX(), player.getY(), player.getSize(), forceTargetDistance > 0 ? forceTargetDistance : agroRatio) || (!forceMultiAttacked && (!isAtMultiArea() || !player.isAtMultiArea()) && (player.getAttackedBy() != this && (player.getAttackedByDelay() > Utils.currentTimeMillis() || player.getFindTargetDelay() > Utils.currentTimeMillis()))) || !clipedProjectile(player, false) || (!forceAgressive && !Wilderness.isAtWild(this) && player.getSkills().getCombatLevelWithSummoning() >= getCombatLevel() * 2))
			    continue;
			possibleTarget.add(player);
		    }
		}
	    }
	    if (checkNPCs) {
		List<Integer> npcsIndexes = World.getRegion(regionId).getNPCsIndexes();
		if (npcsIndexes != null) {
		    for (int npcIndex : npcsIndexes) {
			NPC npc = World.getNPCs().get(npcIndex);
			if (npc == null || npc == this || npc.isDead() || npc.hasFinished() || !Utils.isOnRange(getX(), getY(), size, npc.getX(), npc.getY(), npc.getSize(), forceTargetDistance > 0 ? forceTargetDistance : agroRatio) || !npc.getDefinitions().hasAttackOption() || ((!isAtMultiArea() || !npc.isAtMultiArea()) && npc.getAttackedBy() != this && npc.getAttackedByDelay() > Utils.currentTimeMillis()) || !clipedProjectile(npc, false))
			    continue;
			possibleTarget.add(npc);
		    }
		}
	    }
	}
	return possibleTarget;
    }

    public ArrayList<Entity> getPossibleTargets() {
	return getPossibleTargets(false, true);
    }

    public boolean checkAgressivity() {
	if (!(Wilderness.isAtWild(this) && getDefinitions().hasAttackOption())) {
	    if (!forceAgressive) {
		NPCCombatDefinitions defs = getCombatDefinitions();
		if (defs.getAgressivenessType() == NPCCombatDefinitions.PASSIVE)
		    return false;
	    }
	}
	ArrayList<Entity> possibleTarget = getPossibleTargets();
	if (!possibleTarget.isEmpty()) {
	    Entity target = possibleTarget.get(Utils.random(possibleTarget.size()));
	    setTarget(target);
	    target.setAttackedBy(target);
	    target.setFindTargetDelay(Utils.currentTimeMillis() + 10000);
	    return true;
	}
	return false;
    }

    public boolean isCantInteract() {
	return cantInteract;
    }

    public void setCantInteract(boolean cantInteract) {
	this.cantInteract = cantInteract;
	if (cantInteract)
	    combat.reset();
    }

    public int getCapDamage() {
	return capDamage;
    }

    public void setCapDamage(int capDamage) {
	this.capDamage = capDamage;
    }

    public int getLureDelay() {
	return lureDelay;
    }

    public void setLureDelay(int lureDelay) {
	this.lureDelay = lureDelay;
    }

    public boolean isCantFollowUnderCombat() {
	return cantFollowUnderCombat;
    }

    public void setCantFollowUnderCombat(boolean canFollowUnderCombat) {
	this.cantFollowUnderCombat = canFollowUnderCombat;
    }

    public Transformation getNextTransformation() {
	return nextTransformation;
    }

    @Override
    public String toString() {
	return getDefinitions().getName() + " - " + id + " - " + getX() + " " + getY() + " " + getPlane();
    }

    public boolean isForceAgressive() {
	return forceAgressive;
    }

    public void setForceAgressive(boolean forceAgressive) {
	this.forceAgressive = forceAgressive;
    }

    public int getForceTargetDistance() {
	return forceTargetDistance;
    }

    public void setForceTargetDistance(int forceTargetDistance) {
	this.forceTargetDistance = forceTargetDistance;
    }

    public boolean isForceFollowClose() {
	return forceFollowClose;
    }

    public void setForceFollowClose(boolean forceFollowClose) {
	this.forceFollowClose = forceFollowClose;
    }

    public boolean isForceMultiAttacked() {
	return forceMultiAttacked;
    }

    public void setForceMultiAttacked(boolean forceMultiAttacked) {
	this.forceMultiAttacked = forceMultiAttacked;
    }

	public void setRandomWalk(boolean forceRandomWalk) {
		this.randomwalk = forceRandomWalk;
	}


    public String getCustomName() {
	return name;
    }

    public void setName(String string) {
	this.name = getDefinitions().getName().equals(string) ? null : string;
	changedName = true;
    }

    public int getCustomCombatLevel() {
	return combatLevel;
    }

    public int getCombatLevel() {
	return combatLevel >= 0 ? combatLevel : getDefinitions().combatLevel;
    }

    public String getName() {
	return name != null ? name : getDefinitions().getName();
    }

    public void setCombatLevel(int level) {
	combatLevel = getDefinitions().combatLevel == level ? -1 : level;
	changedCombatLevel = true;
    }

    public boolean hasChangedName() {
	return changedName;
    }

    public boolean hasChangedCombatLevel() {
	return changedCombatLevel;
    }

    public boolean isSpawned() {
	return spawned;
    }

    public void setSpawned(boolean spawned) {
	this.spawned = spawned;
    }

    public boolean isNoDistanceCheck() {
	return noDistanceCheck;
    }

    public void setNoDistanceCheck(boolean noDistanceCheck) {
	this.noDistanceCheck = noDistanceCheck;
    }

    public boolean withinDistance(Player tile, int distance) {
	return super.withinDistance(tile, distance);
    }

    /**
     * Gets the locked.
     * 
     * @return The locked.
     */
    public boolean isLocked() {
	return locked;
    }

    /**
     * Sets the locked.
     * 
     * @param locked
     *            The locked to set.
     */
    public void setLocked(boolean locked) {
	this.locked = locked;
    }

    public boolean isIntelligentRouteFinder() {
	return intelligentRouteFinder;
    }

    public void setIntelligentRouteFinder(boolean intelligentRouteFinder) {
	this.intelligentRouteFinder = intelligentRouteFinder;
    }

    public double getDropRateFactor() {
	return dropRateFactor;
    }

    public void setDropRateFactor(double dropRateFactor) {
	this.dropRateFactor = dropRateFactor;
    }

    public SecondaryBar getNextSecondaryBar() {
	return nextSecondaryBar;
    }

    public void setNextSecondaryBar(SecondaryBar secondaryBar) {
	this.nextSecondaryBar = secondaryBar;
    }
}
